package com.neusoft.bizcore.webauth.secret;

import java.io.IOException;
import java.net.URLEncoder;
import java.util.Arrays;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.GenericFilterBean;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import com.neusoft.bizcore.auth.common.jwt.JwtAuthProvider;

import lombok.extern.slf4j.Slf4j;

/**
 * Logs a principal out.
 * <p>
 * Polls a series of {@link LogoutHandler}s. The handlers should be specified in
 * the order they are required. Generally you will want to call logout handlers
 * <code>TokenBasedRememberMeServices</code> and
 * <code>SecurityContextLogoutHandler</code> (in that order).
 * <p>
 * After logout, a redirect will be performed to the URL determined by either
 * the configured <tt>LogoutSuccessHandler</tt> or the
 * <tt>logoutSuccessUrl</tt>, depending on which constructor was used.
 *
 * @author Ben Alex
 * @author Eddú Meléndez
 */
@Slf4j
public class CasStValidateFilter extends GenericFilterBean {

    private static final String CAS_SERVICE_TICKET_KEY = "ticket";

    // ~ Instance fields
    // ================================================================================================

    private RequestMatcher casStValidateRequestMatcher;
    private final String casServiceUrl;
    private final String casValidateStUrl;
    private final String homeUrl;
    private final JwtAuthProvider jwtAuthProvider;

    public CasStValidateFilter(final JwtAuthProvider jwtAuthProvider, final String filterProcessesUrl,
            final String casServer, final String validateStUrl, final String homeUrl) {
        this.setFilterProcessesUrl(filterProcessesUrl);
        this.casServiceUrl = casServer;
        this.casValidateStUrl = validateStUrl;
        this.homeUrl = homeUrl;
        this.jwtAuthProvider = jwtAuthProvider;
    }

    // ~ Methods
    // ========================================================================================================

    @Override
    public void doFilter(final ServletRequest req, final ServletResponse res, final FilterChain chain)
            throws IOException, ServletException {
        final HttpServletRequest request = (HttpServletRequest) req;
        final HttpServletResponse response = (HttpServletResponse) res;

        if (this.requiresValidateCasSt(request, response)) {
            final String ticket = request.getParameter(CasStValidateFilter.CAS_SERVICE_TICKET_KEY);
            if (StringUtils.isEmpty(ticket)) {
                final String redirectTo = this.casServiceUrl + "?service="
                        + URLEncoder.encode(request.getRequestURL().toString(), "UTF-8");
                CasStValidateFilter.log.info(redirectTo);
                response.sendRedirect(redirectTo);
            } else {
                final String casStValidateUrl = this.casValidateStUrl + "?service="
                        + URLEncoder.encode(request.getRequestURL().toString(), "UTF-8") + "&ticket=" + ticket;
                final CloseableHttpClient client = HttpClientBuilder.create().build();
                final HttpGet get = new HttpGet(casStValidateUrl);
                final HttpResponse validateRes = client.execute(get);
                //CasStValidateFilter.log.info(EntityUtils.toString(validateRes.getEntity()));
                final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
                String username = "";
                try {
                    final DocumentBuilder builder = factory.newDocumentBuilder();
                    final Document doc = builder.parse(validateRes.getEntity().getContent());
                    final NodeList sList = doc.getElementsByTagName("cas:serviceResponse");
                    for (int i = 0; i < sList.getLength(); i++) {
                        final Node node = sList.item(i);
                        if (node.hasChildNodes()) {
                            final NodeList childNodes = node.getChildNodes();
                            for (int j = 0; j < childNodes.getLength(); j++) {
                                final Node child = childNodes.item(j);
                                if ("cas:authenticationSuccess".equals(child.getNodeName())) {
                                    for (int k = 0; k < child.getChildNodes().getLength(); k++) {
                                        if ("cas:user".equals(child.getChildNodes().item(k).getNodeName())) {
                                            username = child.getChildNodes().item(k).getTextContent();
                                            break;
                                        }
                                    }

                                } else if ("cas:authenticationFailure".equals(child.getNodeName())) {
                                    CasStValidateFilter.log.warn("{}", child.getTextContent());
                                    break;
                                }
                            }
                        }
                    }
                } catch (final ParserConfigurationException e) {
                    CasStValidateFilter.log.error("", e);
                } catch (final SAXException e) {
                    CasStValidateFilter.log.error("", e);
                }

                CasStValidateFilter.log.info("cas user: {}", username);

                if (StringUtils.isEmpty(username)) {
                    final String redirectTo = this.casServiceUrl + "?service="
                            + URLEncoder.encode(request.getRequestURL().toString(), "UTF-8");
                    CasStValidateFilter.log.info(redirectTo);
                    response.sendRedirect(redirectTo);
                } else {
                    final String token = this.jwtAuthProvider.generateToken(username, Arrays.asList());
                    final Cookie cookie = new Cookie("Authentication", token);
                    cookie.setDomain(request.getRemoteHost());
                    cookie.setPath("/");
                    cookie.setMaxAge(Integer.MAX_VALUE);
                    response.addCookie(cookie);
                    response.sendRedirect(this.homeUrl);
                }

            }
        } else {
            chain.doFilter(request, response);
        }
    }

    /**
     * Allow subclasses to modify when a logout should take place.
     *
     * @param request the request
     * @param response the response
     * @return <code>true</code> if logout should occur, <code>false</code>
     *         otherwise
     */
    protected boolean requiresValidateCasSt(final HttpServletRequest request,
            final HttpServletResponse response) {
        return this.casStValidateRequestMatcher.matches(request);
    }

    public void setFilterProcessesUrl(final String filterProcessesUrl) {
        this.casStValidateRequestMatcher = new AntPathRequestMatcher(filterProcessesUrl);
    }
}
